#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/serial.h>
#include <sys/select.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <limits.h>
#include <poll.h>
#include <termios.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h>
#include <netinet/tcp.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <linux/sockios.h>

#include "serial_driver.h"

/* RS485 ioctls: */
#ifndef TIOCGRS485
#define TIOCGRS485      0x542E
#endif
#ifndef TIOCSRS485
#define TIOCSRS485      0x542F
#endif

#ifndef TRUE
#define TRUE  1
#define FALSE 0
#endif

int modbus_get_if_ip_by_name(char *if_name, struct sockaddr_in *addr)
{
    int fd;
    struct ifreq ifr;
    
    fd = socket(AF_INET, SOCK_DGRAM, 0);
    
    /* I want to get an IPv4 IP address */
    ifr.ifr_addr.sa_family = AF_INET;
    
    /* I want IP address attached to "eth0" */
    strncpy(ifr.ifr_name, if_name, IFNAMSIZ-1);
    
    ioctl(fd, SIOCGIFADDR, &ifr);
    
    close(fd);
    
    /* display result */
    printf("%s\n", inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr));

    memcpy(addr, &ifr.ifr_addr , sizeof(struct sockaddr_in));
    
    return 0;

}

int serial_open(char *dev,  int speed, Parity mode)
{
    struct termios  setting;
    struct serial_rs485 rs485conf;
    int s_speed;

    memset(&setting, 0, sizeof(setting));
    memset(&rs485conf, 0, sizeof(rs485conf));
    
    /* Open your specific device (e.g., /dev/mydevice): */
    int fd = open (dev, O_RDWR|O_NOCTTY|O_NDELAY);
    if (fd < 0) {
    /* Error handling. See errno. */
        return -1;
    }

    if(0 != tcgetattr(fd, &setting)){
        perror("tcgetattr ");
        goto error_exit;
    }
    /*8 bit data*/
    setting.c_cflag = CS8 | CREAD | CLOCAL | HUPCL;
    /*1 stopbit*/
    setting.c_cflag &= ~CSTOPB;

    /*turn off RTS CTS ctrl*/
    setting.c_cflag &= ~(CRTSCTS);

    /*turn off xon/xoff ctrl*/
    setting.c_iflag &= ~(IXON|IXOFF);
    /*make rasw */
    setting.c_lflag &= ~(ICANON | ECHO | ISIG);
    setting.c_oflag &= ~OPOST;

    switch(speed){
        case 4800:
            s_speed = B4800;
            break;
        case 9600:
            s_speed = B9600;
            break;
        case 19200:
            s_speed = B19200;
            break;
        case 38400:
            s_speed = B38400;
            break;
        case 57600:
            s_speed = B57600;
            break;
        case 115200:
            s_speed = B115200;
            break;
        default:
            printf("(%s:%d) Wrong Speed %d.\n",__FUNCTION__,__LINE__,speed);
    }
    cfsetospeed(&setting, s_speed);
    cfsetispeed(&setting, s_speed);
//    setting.c_cflag |= s_speed;

    switch ( mode ){
        case PARITY_EVEN:
            setting.c_cflag |= PARENB;
            break;
        case PARITY_ODD:
            setting.c_cflag |= PARENB | PARODD;
            break;
        case PARITY_NONE:
        default:
            setting.c_cflag &= ~(PARENB|PARODD);
    }

    cfmakeraw(&setting);
    tcflush(fd, TCIFLUSH);

    if(ioctl(fd, TIOCSLCKTRMIOS, &setting) < 0){
        perror("TIOCSLCKTRMIOS ");
    }
    /* Enable RS485 mode: */
    rs485conf.flags |= SER_RS485_ENABLED;

# if 0
    /* Set logical level for RTS pin equal to 1 when sending: */
    rs485conf.flags |= SER_RS485_RTS_ON_SEND;
    /* or, set logical level for RTS pin equal to 0 when sending: */
    rs485conf.flags &= ~(SER_RS485_RTS_ON_SEND);

    /* Set logical level for RTS pin equal to 1 after sending: */
    rs485conf.flags |= SER_RS485_RTS_AFTER_SEND;
    /* or, set logical level for RTS pin equal to 0 after sending: */
    rs485conf.flags &= ~(SER_RS485_RTS_AFTER_SEND);

    /* Set rts delay before send, if needed: */
    rs485conf.delay_rts_before_send = 1;

    /* Set rts delay after send, if needed: */
    rs485conf.delay_rts_after_send = 1;
#endif
    /* Set this flag if you want to receive data even whilst sending data */
    rs485conf.flags |= SER_RS485_RX_DURING_TX;

    if (ioctl (fd, TIOCSRS485, &rs485conf) < 0) {
        perror("TIOCSRS485 ");
        /* Error handling. See errno. */
    }

    tcflush(fd, TCIOFLUSH);

error_exit:
    
    return fd;

}

void serial_set_blocking (int fd, int should_block)
{
    struct termios tty;
    memset (&tty, 0, sizeof tty);
    if (tcgetattr (fd, &tty) != 0)
    {
            printf("error %d from tggetattr\n", errno);
            return;
    }

    tty.c_cc[VMIN]  = should_block ? 1 : 0;
    tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout

    if (tcsetattr (fd, TCSANOW, &tty) != 0)
            printf ("error %d setting term attributes\n", errno);
}

int serial_close(int fd)
{
    tcflush(fd, TCIFLUSH);;
    return close(fd);
}


int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,
                    B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = { 38400, 19200, 9600, 4800, 2400, 1200, 300, 38400,
                    19200, 9600, 4800, 2400, 1200, 300, };
void set_speed(int fd, int speed)
{
    int i; 
    int status; 
    struct termios Opt;
    
    tcgetattr(fd, &Opt); 
    for ( i=0; i<sizeof(speed_arr)/sizeof(int); i++) 
    { 
        if (speed == name_arr[i]) 
        {     
            tcflush(fd, TCIOFLUSH);     
            cfsetispeed(&Opt, speed_arr[i]);  
            cfsetospeed(&Opt, speed_arr[i]);   
            status = tcsetattr(fd, TCSANOW, &Opt);  
            if (status != 0) 
            {        
                perror("tcsetattr fd1");  
                return;     
            }    
            tcflush(fd,TCIOFLUSH);   
        }  
    }
}

// set serial port data, stop and crc bit.
int set_parity(int fd, int databits, int stopbits, int parity)
{ 
    struct termios options; 
    if ( tcgetattr(fd, &options) != 0)
    { 
        perror("SetupSerial 1");     
        return(FALSE);  
    }
    options.c_cflag &= ~CSIZE; 
    
    //  data bit
    switch (databits)
    {   
        case 7:        
            options.c_cflag |= CS7; 
        break;
        case 8:     
            options.c_cflag |= CS8;
        break;   
        default:    
            fprintf(stderr,"Unsupported data size\n");
        return (FALSE);  
    }
    
    switch (parity) 
    {   
        case 'n':
        case 'N':    
            options.c_cflag &= ~PARENB;   // Clear parity enable
            options.c_iflag &= ~INPCK;    // Enable parity checking 
            options.c_lflag &= 0;  // add comment it's very important to set this parameter.
            options.c_oflag  &= ~OPOST;   /*Output*/
        break;  
        
        case 'o':   
        case 'O':     
            options.c_cflag |= (PARODD | PARENB); // set odd parity  
            options.c_iflag |= INPCK;             // disnable parity checking 
        break;  
        
        case 'e':  
        case 'E':   
            options.c_cflag |= PARENB;     // enable parity     
            options.c_cflag &= ~PARODD;    // change into even     
            options.c_iflag |= INPCK;      // disnable parity checking
        break;
        
        case 'S': 
        case 's':  // as no parity
            options.c_cflag &= ~PARENB;
            options.c_cflag &= ~CSTOPB;break;  
        
        default:   
            fprintf(stderr,"Unsupported parity\n");    
        return (FALSE);  
    }  
    
    // set stop bit  
    switch (stopbits)
    {   
        case 1:    
            options.c_cflag &= ~CSTOPB;  
        break;  
        case 2:    
            options.c_cflag |= CSTOPB;  
        break;
        default:    
            fprintf(stderr,"Unsupported stop bits\n");  
        return (FALSE); 
    } 
    // Set input parity option
    if (parity != 'n')   
        options.c_iflag |= INPCK; 
        tcflush(fd,TCIFLUSH);
        options.c_cc[VTIME] = 150; // set time out for 15 seconds   
        options.c_cc[VMIN] = 0;       // update the options and do it now
    if (tcsetattr(fd,TCSANOW,&options) != 0)   
    { 
        perror("SetupSerial 3");   
        return (FALSE);  
    } 
    return (TRUE);  
}

void config_s(int fd)
{
        struct termios oldtio, newtio;
       
        tcgetattr(fd, &oldtio);
        memset(&newtio, 0, sizeof(newtio));
       
        newtio.c_cflag = B4800 | CS8 | CLOCAL | CREAD | CSTOPB;
        newtio.c_cflag &= ~CRTSCTS;
        newtio.c_iflag = IGNPAR | ICRNL;
        newtio.c_iflag &= ~( IXON | IXOFF | IXANY );
        newtio.c_oflag = 0;
        newtio.c_lflag = ICANON;
       
        tcflush(fd, TCIFLUSH);
        tcsetattr(fd, TCSANOW, &newtio);
}

int serial_open_2(char *dev,  int speed, Parity mode)
{
    /* Open your specific device (e.g., /dev/mydevice): */
    int fd = open (dev, O_RDWR|O_NOCTTY|O_NDELAY);
    if (fd < 0) {
    /* Error handling. See errno. */
        return -1;
    }

    set_speed(fd, 4800);
    config_s(fd);
    if(FALSE== set_parity(fd, 8, 1, 'N')){
        printf("set parity Error\n");
    }
    return fd;
}
